{# Form macros. Native inputs with ``name`` attributes, so every form posts or re-queries through htmx without client-side state. {% from "components/macros/form.html" import field, text_input, select, date_input, primary_button %} #} {% from "components/macros/icons.html" import icon %} {%- set _primary_button_classes -%} text-sm text-aegis-text border border-aegis-teal bg-aegis-teal/10 rounded px-4 py-2.5 hover:bg-aegis-teal/20 transition-colors disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center justify-center gap-2 {%- endset -%} {# Inline primary button (no loading spinner). Defaults to ``type="button"`` so it doesn't accidentally submit a form it's nested inside — pass ``type="submit"`` explicitly when you want submit semantics. ``attrs`` lets the caller pass arbitrary attributes (Alpine ``@click``, ``:disabled``, ``aria-*``) without bloating the macro signature. #} {% macro primary_button(label, type="button", attrs="") %} {% endmacro %} {# Primary submit button with an inline loading spinner. Parent scope must expose ``loading`` (bool) — typically flipped by ``@submit="loading = true"`` on the form. The button disables itself while loading so a double-click can't double-submit. #} {% macro submit_button(idle_label, loading_label) %} {% endmacro %} {# Shared input styling; every control below uses it so the bar reads as one. #} {%- set _control_classes -%} bg-aegis-bg border border-aegis-border rounded px-2.5 py-1.5 text-sm text-aegis-text placeholder:text-aegis-muted/50 focus:border-aegis-teal focus:outline-none transition-colors {%- endset -%} {# A native select. ``options`` are objects or dicts with ``id`` and ``name``, or, ``grouped``, ``{"label", "options"}`` groups of them (an optgroup each); ``blank`` is the "no filter" label (``None`` for no blank choice), chosen while nothing is; ``selected`` the current id. ``label`` wraps it inline for a filter bar; ``block`` lays it out full-width inside a ``field()``. #} {% macro select(name, options, selected=None, blank="All", label=None, block=False, grouped=False) %} {% if label %}{% endif %} {% endmacro %} {% macro date_input(name, value=None, label=None, block=False) %} {% if label %}{% endif %} {% endmacro %} {% macro checkbox(name, checked=False, label=None, value=None) %} {% endmacro %} {# A dropdown of checkboxes, posting ``name`` once per tick: a multi-pick filter. ``options`` are dicts with ``value``, ``label``, ``checked`` and an optional ``count``, and may carry their own ``name`` (a container under its service, ``indent``ed). Kept across the swaps of the form it sits in (``hx-preserve``, which needs the ``id``), so the menu stays open while several are ticked; a click anywhere else closes it. The button at its top ticks every option, or clears them when all are ticked, and the form refreshes as for any tick. #} {% macro checklist(id, name, label, options) %} {% set ticked = options|selectattr("checked")|list|length %}
{{ label }}{% if ticked %}: {{ ticked }}{% endif %}
{{ label }}
{% for o in options %} {% endfor %}
{% endmacro %} {# A search over what is already on the page: as you type it narrows ``target``'s rows to those holding the text and marks each match, with no request, and rows that arrive later follow it (app.js ``data-filter``). For a list the page holds whole (Logs); a list the server pages through searches there instead (``search_input``). Its ``name`` keeps the text in the URL, so a reload or another filter keeps it; the form that holds it leaves it out of its triggers (``change[!target.closest('[data-filter]')]``). #} {% macro filter_input(target, name="q", value=None, placeholder="Filter") %} {% endmacro %} {# A stable id lets htmx restore focus when a search result replaces the form. #} {% macro search_input(name, value=None, placeholder="Search", autofocus=False, attrs="") %} {% endmacro %} {# A labelled field. The control comes via {% call %}; ``error`` renders under it (pattern 1's 422 re-render passes field errors here). #} {% macro field(label, error=None) %}
{% if error %}

{{ error }}

{% endif %}
{% endmacro %} {% macro text_input(name, value=None, placeholder="", required=False, autofocus=False) %} {% endmacro %} {% macro number_input(name, value=None, step=1, min=None, max=None, required=False) %} {% endmacro %} {% macro money_input(name, value=None, placeholder="0.00") %}
{% endmacro %} {% macro textarea(name, value=None, rows=6, placeholder="") %} {% endmacro %} {# Themed combobox — replaces native {% endif %}
{% if grouped %} {% else %} {% endif %}
{% endmacro %} {# Password input with a show/hide eye toggle. Standalone so callers can wire it up with either ``name=`` (plain form post) or Alpine ``x_model`` bindings. The toggle swaps the rendered ``type`` via ``:type`` so browser password managers still recognize the field as a password — Alpine's ``:type`` takes effect after the initial parse, so autofill works. ``bg_class`` lets callers match the surrounding surface. #} {% macro password_input(name=None, x_model=None, autocomplete=None, required=True, minlength=None, placeholder="", bg_class="bg-aegis-card") %}
{% endmacro %} {# "OR" divider with hairlines — sits between a form and an alternate action (e.g. email/password above, OAuth buttons below). #} {% macro or_divider() %}
or
{% endmacro %} {# The time-window chips: a row of exclusive pills that submits as one radio field, so a click re-requests the enclosing form. Same recipe as ``layout.chip``, driven by ``peer-checked`` because a radio rather than a link marks the choice. ``ranges`` are ``(value, label)``, or ``(value, label, icon)`` for a chip that shows its icon (``icons.html``), its label on hover and to a reader. #} {% macro range_chips(ranges, days, field="days", aria="Time window") %}
{% for choice in ranges %} {% set value, label = choice[0], choice[1] %} {% set glyph = choice[2] if choice|length > 2 else none %} {% endfor %}
{% endmacro %} {# The time-window row that re-requests ``url`` when a chip is picked and swaps ``target`` (``hx_replace``), the choice kept in the address as ``field``. #} {% macro range_form(ranges, current, url, target, field="days", aria="Time window", id=None, class="") %}
{{ range_chips(ranges, current, field=field, aria=aria) }}
{% endmacro %} {# A small action button (row verbs, card verbs, toolbar). ``attrs`` is the htmx verb; ``vals`` a dict for ``hx-vals``; ``tone`` is default (quiet), primary (teal), danger or warn; ``glyph`` draws an icon in place of the label, which then names it for a reader and on hover (as the chips do). The one recipe every compact button uses. #} {% macro action(label, attrs, tone="default", vals=None, glyph=None) %} {% set tones = {"default": "border-aegis-border text-aegis-muted hover:text-aegis-text hover:border-aegis-muted", "primary": "border-aegis-teal text-aegis-text hover:bg-aegis-teal/10", "danger": "border-aegis-error/40 text-error hover:border-aegis-error", "warn": "border-aegis-amber/40 text-aegis-text hover:border-aegis-amber"} %} {% endmacro %}